/*-------------------------------------------------------------------------
 *
 * proc.h
 *	  每个进程的共享内存数据结构
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/include/storage/proc.h
 *
 *-------------------------------------------------------------------------
 */
#ifndef _PROC_H_
#define _PROC_H_

#include "access/clog.h"
#include "access/xlogdefs.h"
#include "lib/ilist.h"
#include "storage/latch.h"
#include "storage/lock.h"
#include "storage/pg_sema.h"
#include "storage/proclist_types.h"

/*
 * 每个后端为其当前顶层事务的非中止子事务最多宣传PGPROC_MAX_CACHED_SUBXIDS个TransactionIds。
 * 这些必须被其他后端视为正在运行的XID。
 *
 * 我们还跟踪缓存是否溢出（即，事务生成了至少一个无法放入缓存的子事务）。
 * 如果没有缓存溢出，我们可以假定在PGPROC数组中未列出的XID不是正在运行的事务。
 * 否则，我们必须查看pg_subtrans。
 */
#define PGPROC_MAX_CACHED_SUBXIDS 64	/* XXX 猜测的值 */

typedef struct XidCacheStatus
{
	/* 缓存的subxids数量，永远不超过PGPROC_MAX_CACHED_SUBXIDS */
	uint8		count;
	/* PGPROC->subxids是否溢出 */
	bool		overflowed;
} XidCacheStatus;

struct XidCache
{
	TransactionId xids[PGPROC_MAX_CACHED_SUBXIDS];
};

/*
 * PGPROC->statusFlags和PROC_HDR->statusFlags[]的标志
 */
#define		PROC_IS_AUTOVACUUM	0x01	/* 它是一个autovac工作进程吗？ */
#define		PROC_IN_VACUUM		0x02	/* 当前正在运行的惰性清理 */
#define		PROC_IN_SAFE_IC		0x04	/* 当前正在运行的CREATE INDEX
										 * CONCURRENTLY或REINDEX
										 * CONCURRENTLY在非表达式，
										 * 非部分索引上 */
#define		PROC_VACUUM_FOR_WRAPAROUND	0x08	/* 仅由autovac设置 */
#define		PROC_IN_LOGICAL_DECODING	0x10	/* 当前正在进行逻辑
												 * 解码，没有xact */
#define		PROC_AFFECTS_ALL_HORIZONS	0x20	/* 此进程的xmin必须
												 * 包含在所有数据库的清理范围内 */

/* 在EOXact时重置的标志 */
#define		PROC_VACUUM_STATE_MASK \
	(PROC_IN_VACUUM | PROC_IN_SAFE_IC | PROC_VACUUM_FOR_WRAPAROUND)

/*
 * 与Xmin相关的标志。确保任何影响进程的Xmin
 * 值在VACUUM中如何被解释的标志都包含在这里。
 */
#define		PROC_XMIN_FLAGS (PROC_IN_VACUUM | PROC_IN_SAFE_IC)

/*
 * 我们允许少量“弱”关系锁（AccessShareLock，
 * RowShareLock，RowExclusiveLock）记录在PGPROC结构中
 * 而不是主锁表中。这减轻了锁管理器LWLocks的争用。
 * 有关其他详细信息，请参见storage/lmgr/README。
 */
#define		FP_LOCK_SLOTS_PER_BACKEND 16

/*
 * 一个无效的pgprocno。 必须大于我们可能拥有的最大PGPROC
 * 结构的数量。 请参见有关MAX_BACKENDS的注释。
 */
#define INVALID_PGPROCNO		PG_INT32_MAX


/*
 * PGPROC.delayChkpt的标志
 *
 * 这些标志可以用来延迟检查点的开始或完成
 * 短时间。 如果相应的位在
 * 任何后端的PGPROC中被设置，则该标志有效。
 *
 * 就我们的目的而言，检查点有三个阶段：(1) 确定
 * 重做指针将要移动的位置，(2) 将所有数据
 * 永久写入磁盘，以及 (3) WAL日志检查点。
 *
 * 设置DELAY_CHKPT_START阻止系统从阶段1
 * 移动到阶段2。当我们在执行WAL记录的
 * 数据修改，并将在阶段2中刷新到磁盘时，这很有用。通过在
 * 写入WAL之前设置该标志，并在我们既写入了WAL又
 * 执行了相应的修改后清除它，我们确保如果WAL记录
 * 在新的重做点之前插入，则相应的数据更改也将
 * 在检查点完成之前刷新到磁盘。 （在
 * 极其常见的情况下，所修改的数据位于共享缓冲区中，
 * 我们在写入WAL之前对子相关缓冲区获取独占内容锁，
 * 这个机制是不必要的，因为阶段2将在我们释放
 * 内容锁后阻塞，然后将修改后的数据刷新到
 * 磁盘。）
 *
 * 设置DELAY_CHKPT_COMPLETE阻止系统从阶段2
 * 移动到阶段3。这在我们执行WAL记录操作时很有用，
 * 这可能会使缓冲区失效，例如表关系截断。在这种情况下，
 * 我们需要确保任何被失效的缓冲区因此未被检查点刷新
 * 实际上在磁盘上被销毁。重放可以处理不存在的文件
 * 或块，但不能处理内容错误的块。
 */
#define DELAY_CHKPT_START		(1<<0)
#define DELAY_CHKPT_COMPLETE	(1<<1)

typedef enum
{
	PROC_WAIT_STATUS_OK,
	PROC_WAIT_STATUS_WAITING,
	PROC_WAIT_STATUS_ERROR,
} ProcWaitStatus;

/*
 * 每个后端在共享内存中都有一个PGPROC结构。还有一个当前未使用
 * 的PGPROC结构列表，将被重新分配给新的后端。
 *
 * links：PGPROC所在列表的列表链接。当等待锁时，
 * PGPROC被链接到该锁的waitProcs队列中。被回收的PGPROC
 * 被链接到ProcGlobal的freeProcs列表中。
 *
 * 注意：twophase.c还为每个当前已准备的事务
 * 设置了一个虚拟PGPROC结构。这些PGPROC出现在ProcArray
 * 数据结构中，以使得准备的事务看起来仍在运行，
 * 并正确显示为持有锁。一个准备事务PGPROC可以
 * 通过pid == 0的特性与真实的PGPROC区分开。准备事务
 * PGPROC中的信号量和锁活动字段未使用，
 * 但是它的myProcLocks[]列表是有效的。
 *
 * 我们允许在没有锁的情况下访问该结构的许多字段，例如
 * delayChkpt和isBackgroundWorker。然而，请记住，
 * 写入镜像（见下文）需要持有ProcArrayLock或XidGenLock
 * 至少在共享模式下，以便pgxactoff不会并发更改。
 *
 * 镜像字段：
 *
 * PGPROC中的一些字段（见“镜像...”注释）被镜像到一个
 * 更紧凑的ProcGlobal数组的元素中。这些数组按
 * PGPROC->pgxactoff索引。两个副本需要保持一致。
 *
 * 注意：pgxactoff索引值在没有持有
 * 锁的情况下*绝对不能*访问。
 *
 * 详情请参见PROC_HDR。
 */
struct PGPROC
{
	/* proc->links 必须位于结构的第一位（见ProcSleep，ProcWakeup等） */
	SHM_QUEUE	links;			/* 如果进程在列表中，则为列表链接 */
	PGPROC	  **procgloballist; /* 拥有此PGPROC的procglobal列表 */

	PGSemaphore sem;			/* 一个信号量用于等待 */
	ProcWaitStatus waitStatus;

	Latch		procLatch;		/* 进程的通用锁 */


	TransactionId xid;			/* 当前由此进程执行的顶层事务的 ID，如果正在运行且分配了 XID；否则为 InvalidTransactionId。 
								 * 在 ProcGlobal->xids[pgxactoff] 中镜像 */

	TransactionId xmin;			/* 在我们开始事务时的最小运行 XID，不包括 LAZY VACUUM： 
								 * vacuum 不能删除由 xid >= xmin 删除的元组！ */

	LocalTransactionId lxid;	/* 当前由此进程执行的顶层事务的本地 ID，如果正在运行；
								 * 否则为 InvalidLocalTransactionId */
	int			pid;			/* 后端的进程 ID；如果是准备好的事务则为 0 */

	int			pgxactoff;		/* 在各种 ProcGlobal->arrays 中的偏移量，
								 * 与此 PGPROC 镜像的数据相对应 */
	int			pgprocno;

	/* 当后端仍在启动时，这些字段为零： */
	BackendId	backendId;		/* 此后端的后端 ID（如果已分配） */
	Oid			databaseId;		/* 此后端正在使用的数据库的 OID */
	Oid			roleId;			/* 使用此后端的角色的 OID */

	Oid			tempNamespaceId;	/* 此后端正在使用的临时模式的 OID */

	bool		isBackgroundWorker; /* 如果不是常规后端则为真。 */

	/*
	 * 在热备份模式下，表示已为当前事务发送了冲突信号。
	 * 在持有 ProcArrayLock 时设置/清除，但不是必需的。如有需要，可在无锁状态下访问。
	 */
	bool		recoveryConflictPending;

	/* 进程当前等待的 LWLock 的信息（如果有） */
	uint8		lwWaiting;		/* 见 LWLockWaitState */
	uint8		lwWaitMode;		/* 正在等待的 lwlock 模式 */
	proclist_node lwWaitLink;	/* LW 锁等待列表中的位置 */

	/* 对条件变量的支持。 */
	proclist_node cvWaitLink;	/* 在CV等待列表中的位置 */

	/* 进程当前等待的锁的信息（如果有） */
	/* waitLock 和 waitProcLock 在不等待时为 NULL。 */
	LOCK	   *waitLock;		/* 我们正在等待的锁对象 ... */
	PROCLOCK   *waitProcLock;	/* 等待锁的每个持有者的信息 */
	LOCKMODE	waitLockMode;	/* 我们正在等待的锁的类型 */
	LOCKMASK	heldLocks;		/* 此后端在此锁对象上已持有的锁类型的位掩码 */
	pg_atomic_uint64 waitStart; /* 等待锁获取开始的时间 */

	int			delayChkptFlags;	/* 对于 DELAY_CHKPT_* 标志 */

	uint8		statusFlags;	/* 此后端的状态标志，见上面的 PROC_*。在 
								 * ProcGlobal->statusFlags[pgxactoff] 中镜像 */

	/*
	 * 允许我们在需要时等待同步复制的信息。
	 * waitLSN 如果不等待则为 InvalidXLogRecPtr；仅由用户后端设置。
	 * syncRepState 只能由拥有进程或 WALSender 触摸。
	 * syncRepLinks 仅在持有 SyncRepLock 时使用。
	 */
	XLogRecPtr	waitLSN;		/* 等待此 LSN 或更高 */
	int			syncRepState;	/* 同步复制的等待状态 */
	SHM_QUEUE	syncRepLinks;	/* 列出如果进程在同步复制队列中的链接 */

	/*
	 * 此后端持有或等待的所有PROCLOCK对象都链接到这些列表中的一个，
	 * 根据它们锁的分区号。
	 */
	SHM_QUEUE	myProcLocks[NUM_LOCK_PARTITIONS];

	XidCacheStatus subxidStatus;	/* 与
									 * ProcGlobal->subxidStates[i] 镜像 */
	struct XidCache subxids;	/* 子事务XID的缓存 */

	/* 支持组XID清除。 */
	/* 如果是ProcArray组的成员并且在等待XID清除，则为true */
	bool		procArrayGroupMember;
	/* 下一个等待XID清除的ProcArray组成员 */
	pg_atomic_uint32 procArrayGroupNext;

	/*
	 * 在事务的主XID和子事务之间
	 * 最新的事务ID
	 */
	TransactionId procArrayGroupMemberXid;

	uint32		wait_event_info;	/* proc的等待信息 */

	/* 支持组事务状态更新。 */
	bool		clogGroupMember;	/* 如果是clog组的成员，则为true */
	pg_atomic_uint32 clogGroupNext; /* 下一个clog组成员 */
	TransactionId clogGroupMemberXid;	/* clog组成员的事务ID */
	XidStatus	clogGroupMemberXidStatus;	/* clog组成员的事务状态 */
	int			clogGroupMemberPage;	/* 对应于clog组成员事务ID的clog页面 */
	XLogRecPtr	clogGroupMemberLsn; /* clog组成员的提交记录的WAL位置 */

	/* 锁管理器数据，记录此后端获取的快速路径锁。 */
	LWLock		fpInfoLock;		/* 保护每个后端的快速路径状态 */
	uint64		fpLockBits;		/* 为每个快速路径槽持有的锁模式 */
	Oid			fpRelId[FP_LOCK_SLOTS_PER_BACKEND]; /* rel oids的槽 */
	bool		fpVXIDLock;		/* 我们是否持有快速路径VXID锁？ */
	LocalTransactionId fpLocalTransactionId;	/* 快速路径VXID锁的lxid */

	/*
	 * 支持锁组。 使用LockHashPartitionLockByProc在组
	 * 领导者上获取保护这些字段的LWLock。
	 */
	PGPROC	   *lockGroupLeader;	/* 锁组领导者，如果我是成员 */
	dlist_head	lockGroupMembers;	/* 如果我是领导者，成员列表 */
	dlist_node	lockGroupLink;	/* 如果我是成员，我的成员链接 */
};

/* 注意：“typedef struct PGPROC PGPROC”出现在storage/lock.h中。 */


extern PGDLLIMPORT PGPROC *MyProc;

/*
 * 整个数据库集群只有一个 ProcGlobal 结构。
 *
 * 向 procarray 中添加/移除条目需要同时持有
 * ProcArrayLock 和 XidGenLock 的独占模式（按该顺序）。两个都是
 * 必需的，因为稠密数组（见下文）是从
 * GetNewTransactionId() 和 GetSnapshotData() 访问的，我们不想通过
 * 同时使用相同锁来增加进一步的竞争。添加/移除 procarray
 * 条目要频繁得多。
 *
 * PGPROC 中的某些字段被映射到更紧密的数组中（例如
 * xids），每个后端有一个条目。这些数组只包含已通过
 * ProcArrayAdd() 添加到共享数组的 PGPROCs
 * （与包含有间隔的未使用 PGPROCs 的 PGPROC 数组形成对比）。
 *
 * 稠密数组由 PGPROC->pgxactoff 索引。任何并发的
 * ProcArrayAdd() / ProcArrayRemove() 都可能导致 procarray
 * 成员的 pgxactoff 更改。因此，仅在持有
 * ProcArrayLock 或 XidGenLock 时，使用 PGPROC->pgxactoff
 * 访问稠密数组是安全的。
 *
 * 只要 PGPROC 在 procarray 中，镜像值需要以一致的方式
 * 在两个地方维护。
 *
 * 更密集的单独数组在三个主要原因上是有益的：首先，尽可能允许访问
 * 数据的紧密循环。其次，防止频繁变化的数据（如 xmin）的更新
 * 使包含不太频繁变化的数据（如 xid，
 * statusFlags）的缓存行失效。第三，将频繁访问的数据浓缩到尽可能少的
 * 缓存行中。
 *
 * 在这些稠密数组和 PGPROC 之间镜像数据的两个主要原因：
 * 首先，如上所述，PGPROC 的数组条目只能在持有
 * ProcArrayLock 或 XidGenLock 时访问，而 PGPROC 条目则不需要那样
 * （显然，也可能存在单个字段周围的锁定要求，与这里的关注点
 * 分开）。这对后端高效检查自己值尤其重要，因为它们通常可以
 * 在不锁定的情况下安全地进行。其次，PGPROC 字段允许避免对
 * 稠密数组的不必要访问和修改。后端自己的 PGPROC 更可能
 * 在本地缓存中，而稠密数组的缓存行将被其他
 * 后端修改（通常从其他核心/插槽的缓存中移除）。在
 * 提交/中止时，检查 PGPROC 值可以避免访问/污染
 * 相应的数组值。
 *
 * 基本上，在检查单个后端的数据时访问 PGPROC 变量是有意义的，
 * 特别是在出于其他原因查看 PGPROC 时。尤其当我们需要查看许多
 * / 大多数条目时，查看“稠密”数组是有意义的，因为我们
 * 可以从减少间接性和更好的跨进程缓存能力中受益。
 *
 * 当通过 ProcArrayAdd() 进入 PGPROC 用于 2PC 事务时，
 * 稠密数组中的数据从 PGPROC 初始化，同时它已经持有
 * ProcArrayLock。
 */
typedef struct PROC_HDR
{
	/* PGPROC 结构数组（不包括为预备事务保留的虚拟条目） */
	PGPROC	   *allProcs;

	/* 数组镜像当前procarray中每个PGPROC的PGPROC.xid */
	TransactionId *xids;

	/*
	 * 数组镜像当前procarray中每个PGPROC的PGPROC.subxidStatus。
	 */
	XidCacheStatus *subxidStates;

	/*
	 * 数组镜像当前procarray中每个PGPROC的PGPROC.statusFlags。
	 */
	uint8	   *statusFlags;

	/* allProcs数组的长度 */
	uint32		allProcCount;
	/* 空闲PGPROC结构的列表头 */
	PGPROC	   *freeProcs;
	/* autovacuum的空闲PGPROC结构的列表头 */
	PGPROC	   *autovacFreeProcs;
	/* bgworker的空闲PGPROC结构的列表头 */
	PGPROC	   *bgworkerFreeProcs;
	/* walsender的空闲PGPROC结构的列表头 */
	PGPROC	   *walsenderFreeProcs;
	/* 等待组XID清除的第一个pgproc */
	pg_atomic_uint32 procArrayGroupFirst;
	/* 等待组事务状态更新的第一个pgproc */
	pg_atomic_uint32 clogGroupFirst;
	/* WALWriter进程的闩锁 */
	Latch	   *walwriterLatch;
	/* Checkpointer进程的闩锁 */
	Latch	   *checkpointerLatch;
	/* 当前对适当的spins_per_delay值的共享估计 */
	int			spins_per_delay;
	/* 启动进程等待定位的缓冲区的缓冲区ID，或-1 */
	int			startupBufferPinWaitBufId;
} PROC_HDR;

extern PGDLLIMPORT PROC_HDR *ProcGlobal;

extern PGDLLIMPORT PGPROC *PreparedXactProcs;

/* 给定pgprocno的PGPROC的访问器。 */
#define GetPGProcByNumber(n) (&ProcGlobal->allProcs[(n)])

/*
 * 我们为辅助进程留出一些额外的PGPROC结构，
 * 即那些不是完整后端但需要共享内存访问的东西。
 *
 * 背景写入器、检查点、WAL写入器和归档器在正常操作期间运行。
 * 启动进程和WAL接收器也消耗2个插槽，但WAL写入器仅在启动已退出后启动，
 * 所以我们只需要5个插槽。
 */
#define NUM_AUXILIARY_PROCS		5

/* 可配置选项 */
extern PGDLLIMPORT int DeadlockTimeout;
extern PGDLLIMPORT int StatementTimeout;
extern PGDLLIMPORT int LockTimeout;
extern PGDLLIMPORT int IdleInTransactionSessionTimeout;
extern PGDLLIMPORT int IdleSessionTimeout;
extern PGDLLIMPORT bool log_lock_waits;


/*
 * 函数原型
 */
extern int	ProcGlobalSemas(void);
extern Size ProcGlobalShmemSize(void);
extern void InitProcGlobal(void);
extern void InitProcess(void);
extern void InitProcessPhase2(void);
extern void InitAuxiliaryProcess(void);

extern void SetStartupBufferPinWaitBufId(int bufid);
extern int	GetStartupBufferPinWaitBufId(void);

extern bool HaveNFreeProcs(int n);
extern void ProcReleaseLocks(bool isCommit);

extern void ProcQueueInit(PROC_QUEUE *queue);
extern ProcWaitStatus ProcSleep(LOCALLOCK *locallock, LockMethod lockMethodTable);
extern PGPROC *ProcWakeup(PGPROC *proc, ProcWaitStatus waitStatus);
extern void ProcLockWakeup(LockMethod lockMethodTable, LOCK *lock);
extern void CheckDeadLockAlert(void);
extern bool IsWaitingForLock(void);
extern void LockErrorCleanup(void);

extern void ProcWaitForSignal(uint32 wait_event_info);
extern void ProcSendSignal(int pgprocno);

extern PGPROC *AuxiliaryPidGetProc(int pid);

extern void BecomeLockGroupLeader(void);
extern bool BecomeLockGroupMember(PGPROC *leader, int pid);

#endif							/* _PROC_H_ */
